import CryptoJS from 'crypto-js';

/**
 * 讯飞方言大模型实时语音识别服务 (WebSocket)
 * 封装了 WebSocket 连接、认证、流式传输和错误处理
 */
export default class IflytekSpeechService {
  constructor(options = {}) {
    // 从文档中获取的示例凭证，实际使用时应由外部传入
    this.APPID = options.appid || '9f230f94';
    this.APISecret = options.apiSecret || 'OWQ1YTRiMGM2MWNhMmVjOTM3ZjdmOWFl';
    this.APIKey = options.apiKey || '94b9d70ea1706aaa01d9333ef60946be';
    
    this.apiUrl = 'wss://iat.cn-huabei-1.xf-yun.com/v1';
    this.host = 'iat.cn-huabei-1.xf-yun.com';

    this.socketTask = null;
    this.isConnecting = false;
    this.isRecognizing = false;
    this.finalRecognizedText = '';

    // 用于处理 Promise 回调
    this.resolvePromise = null;
    this.rejectPromise = null;
  }

  /**
   * 生成符合讯飞要求的鉴权URL
   * @returns {string} 鉴权后的WebSocket URL
   */
  getAuthorizationUrl() {
    const date = new Date().toUTCString();
    
    // 1. 构造待签名的字符串
    const signatureOrigin = `host: ${this.host}\ndate: ${date}\nGET /v1 HTTP/1.1`;

    // 2. 使用 HMAC-SHA256 加密
    const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.APISecret);
    const signature = CryptoJS.enc.Base64.stringify(signatureSha);

    // 3. 构造 authorization 参数
    const authorizationOrigin = `api_key="${this.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;
    const authorization = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(authorizationOrigin));

    // 4. 拼接最终 URL
    const url = `${this.apiUrl}?authorization=${authorization}&date=${encodeURIComponent(date)}&host=${this.host}`;
    return url;
  }
  
  /**
   * 核心方法：识别音频文件
   * @param {string} filePath - uni.getRecorderManager() 返回的音频文件临时路径
   * @returns {Promise<object>} - 包含识别结果的Promise
   */
  recognizeAudio(filePath) {
    if (this.isConnecting || this.isRecognizing) {
      console.warn('[IflytekSpeechService] 正在进行语音识别，请勿重复调用。');
      return Promise.reject({ code: 'IN_PROGRESS', message: '正在识别中' });
    }
    
    return new Promise((resolve, reject) => {
      this.resolvePromise = resolve;
      this.rejectPromise = reject;
      this.finalRecognizedText = ''; // 重置最终结果

      uni.showLoading({
        title: '正在识别...'
      });

      const url = this.getAuthorizationUrl();
      console.log('[IflytekSpeechService] Connecting to:', url);

      this.socketTask = uni.connectSocket({
        url: url,
        success: () => {},
        fail: (err) => {
          console.error('[IflytekSpeechService] 创建WebSocket连接失败:', err);
          uni.hideLoading();
          this.rejectPromise({ code: 'CONNECT_FAIL', message: '创建连接失败' });
        }
      });
      
      this.isConnecting = true;

      this.socketTask.onOpen(() => {
        this.isConnecting = false;
        this.isRecognizing = true;
        console.log('[IflytekSpeechService] WebSocket 已连接，准备发送音频数据');
        this.sendAudioData(filePath);
      });

      this.socketTask.onMessage((res) => {
        try {
            const result = JSON.parse(res.data);
            console.log('[IflytekSpeechService] 收到消息:', result);

            // 1. 检查是否是错误消息
            if (result.header && result.header.code !== 0) {
                console.error(`[IflytekSpeechService] 识别失败: ${result.header.code} - ${result.header.message}`);
                uni.hideLoading();
                this.rejectPromise({ code: result.header.code, message: result.header.message });
                this.closeConnection();
                return;
            }

            // 2. 检查是否有识别结果
            if (result.payload && result.payload.result && result.payload.result.text) {
                this.processRecognitionResult(result);
            }

            // 3. 检查是否是会话结束消息
            if (result.header && result.header.status === 2) {
                 console.log('[IflytekSpeechService] 识别完成:', this.finalRecognizedText);
                 uni.hideLoading();
                 this.resolvePromise({
                     text: this.finalRecognizedText,
                     raw: result
                 });
                 this.closeConnection();
            }
            // 其他情况（如 status=0, 1 的中间消息但无 payload）则忽略，继续等待
        } catch (e) {
            console.error('[IflytekSpeechService] 解析消息失败:', e);
        }
      });

      this.socketTask.onError((err) => {
        this.isConnecting = false;
        this.isRecognizing = false;
        console.error('[IflytekSpeechService] WebSocket 连接发生错误:', err);
        uni.hideLoading();
        this.rejectPromise({ code: 'WEBSOCKET_ERROR', message: 'WebSocket连接错误' });
      });

      this.socketTask.onClose((e) => {
        this.isConnecting = false;
        this.isRecognizing = false;
        console.log('[IflytekSpeechService] WebSocket 已关闭', e);
        // 如果 promise 还没有被处理，说明是异常关闭
        if (this.rejectPromise && !this.finalRecognizedText) {
             this.rejectPromise({ code: e.code, message: `连接意外断开: ${e.reason}` });
        }
      });
    });
  }

  /**
   * 处理识别结果
   * @param {object} result - 服务端返回的消息体
   */
  processRecognitionResult(result) {
    const textBase64 = result.payload.result.text;
    const textStr = CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(textBase64));
    const textObj = JSON.parse(textStr);
    
    let currentText = '';
    textObj.ws.forEach(wordInfo => {
        wordInfo.cw.forEach(charInfo => {
            currentText += charInfo.w;
        });
    });

    // 简化处理逻辑：直接使用当前返回的结果覆盖，以处理短音频的最终结果
    this.finalRecognizedText = currentText;

    // 如果是最后一句 (ls=true) 并且识别内容不为空，才添加句号
    if (textObj.ls && this.finalRecognizedText) {
      this.finalRecognizedText += '。';
    }
  }
  
  /**
   * 读取本地音频文件并一次性发送
   * @param {string} filePath 
   */
  sendAudioData(filePath) {
    uni.getFileSystemManager().readFile({
      filePath: filePath,
      success: (res) => {
        const arrayBuffer = res.data;
        const base64Audio = uni.arrayBufferToBase64(arrayBuffer);

        // 1. 发送第一帧 (包含所有音频数据)
        const firstFrameData = {
          header: {
            app_id: this.APPID,
            status: 0,
          },
          parameter: {
            iat: {
              language: "zh_cn",
              accent: "mulacc",
              domain: "slm",
              result: {
                encoding: "utf8",
                compress: "raw",
                format: "json"
              }
            }
          },
          payload: {
            audio: {
              encoding: "lame", // 指定为mp3格式
              sample_rate: 16000,
              channels: 1,
              bit_depth: 16,
              seq: 0,
              audio: base64Audio,
            }
          }
        };
        this.socketTask.send({data: JSON.stringify(firstFrameData)});

        // 2. 发送结束帧
        const lastFrameData = {
            header: {
                app_id: this.APPID,
                status: 2,
            },
            payload: {
                audio: {
                    seq: 1,
                    audio: ""
                }
            }
        };
        // 在发送完音频数据后立即发送结束帧
        this.socketTask.send({data: JSON.stringify(lastFrameData)});
        console.log('[IflytekSpeechService] 音频数据发送完毕，已发送停止指令。');
      },
      fail: (err) => {
        uni.hideLoading();
        console.error('[IflytekSpeechService] 读取音频文件失败:', err);
        this.rejectPromise({ code: 'FILE_READ_ERROR', message: '读取音频文件失败' });
      }
    });
  }

  /**
   * 关闭WebSocket连接并清理资源
   */
  closeConnection() {
    if (this.socketTask) {
      this.socketTask.close();
    }
    this.socketTask = null;
    this.isRecognizing = false;
    this.isConnecting = false;
    this.resolvePromise = null;
    this.rejectPromise = null;
  }

  /**
   * 工具方法：根据错误码获取友好的错误提示
   * @param {number|string} errorCode - API返回的错误码
   * @returns {string} - 对应的错误提示文本
   */
  getErrorMessage(errorCode) {
    const errorMessages = {
      10114: '会话超时，请重试',
      10139: '参数错误，请检查代码',
      10313: 'AppID和APIKey不匹配，请检查凭证',
      11200: '功能未授权，请确认已开通服务',
      11201: '调用次数超限',
      'IN_PROGRESS': '正在识别中，请稍候',
      'CONNECT_FAIL': '服务器连接失败',
      'WEBSOCKET_ERROR': '网络通信发生错误',
      'FILE_READ_ERROR': '读取录音文件失败',
    };
    return errorMessages[errorCode] || `发生未知错误(${errorCode})，请重试`;
  }
} 